package com.open.capacity.notice.provider.core.http;

import java.io.IOException;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.function.BiFunction;
import java.util.function.Consumer;

import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;

import org.apache.http.HttpException;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.CollectionUtils;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Maps;
import com.open.capacity.notice.provider.core.constant.ClientErrorEnum;
import com.open.capacity.notice.provider.core.constant.CommonConstant;
import com.open.capacity.notice.provider.core.http.interactive.req.ThRequest;
import com.open.capacity.notice.provider.core.http.interactive.res.ThResponse;

import lombok.extern.slf4j.Slf4j;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.ConnectionPool;
import okhttp3.HttpUrl;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Protocol;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;

/**
 * ThOkHttp3Template 常规okhttp请求模板
 */
@Slf4j
public class ThOkHttp3Template implements InitializingBean {

	protected OkHttpClient okhttp3Client;
	protected ObjectMapper objectMapper;

	public ThOkHttp3Template(OkHttpClient okhttp3Client, ObjectMapper objectMapper) {
		this.okhttp3Client = okhttp3Client;
		this.objectMapper = objectMapper;
	}

	public ObjectMapper getObjectMapper() {
		return objectMapper;
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		// 设置连接池，最大连接数为10，连接空闲存活时间为5分钟
		ConnectionPool connectionPool = new ConnectionPool(10, 5, TimeUnit.MINUTES);
		// 创建 OkHttpClient 实例，并进行配置
		okhttp3Client = new OkHttpClient.Builder().connectTimeout(1, TimeUnit.SECONDS) // 连接超时时间设置为10秒
				.pingInterval(1, TimeUnit.MINUTES) // 心跳间隔设置为1分钟
				.readTimeout(30, TimeUnit.SECONDS) // 读取超时时间设置为30秒
				.writeTimeout(30, TimeUnit.SECONDS) // 写入超时时间设置为30秒
				.retryOnConnectionFailure(true) // 启用连接失败重试
				.connectionPool(connectionPool) // 配置连接池
				.build();
	}

	public <API extends ApiAdress, RES extends ThResponse> RES get(API address, Map<String, Object> params,
			Class<RES> rtClass, Object... args) throws IOException {
		String url = address.getUrl(args);
		RES res = this.doRequest(address.getUrl(args), HttpMethod.GET, null, params, null, rtClass);
		if (log.isDebugEnabled()) {
			log.debug("ThOkHttp3 {} >> debugger, url : {}, Code : {}", address.getOpt(), url, res.getCode());
		}
		return res;
	}

	public <REQ extends ThRequest, RES extends ThResponse, API extends ApiAdress> RES post(API address,
			Map<String, Object> headers, Map<String, Object> params, Map<String, Object> bodyContent,
			Class<RES> rtClass, Object... args) throws IOException {
		String url = address.getUrl(args);
		RES res = this.doRequest(url, HttpMethod.POST, headers, params, bodyContent, rtClass);
		if (log.isDebugEnabled()) {
			log.debug("ThOkHttp3 {} >> debugger, url : {}, requestBody : {}, Code : {}", address.getOpt(), url,
					bodyContent, res.getCode());
		}
		return res;
	}

	public <API extends ApiAdress, RES extends ThResponse> void asyncGet(API address, boolean isAuth,
			Map<String, Object> headers, Map<String, Object> queryParams, Class<RES> cls, Consumer<RES> success,
			Consumer<IOException> failure, Object... args) throws IOException {
		String url = address.getUrl(args);
		doAsyncRequest(url, HttpMethod.GET, headers, queryParams, Maps.newHashMap(), success, failure, cls);
	}

	public <RES extends ThResponse, API extends ApiAdress> void asyncPost(API address, boolean isAuth,
			Map<String, Object> headers, Map<String, Object> queryParams, Map<String, Object> requestBody,
			Class<RES> cls, Consumer<RES> success, Consumer<IOException> failure, Object... args) throws IOException {
		String url = address.getUrl(args);
		doAsyncRequest(url, HttpMethod.POST, headers, queryParams, requestBody, success, failure, cls);
	}

	public <RES extends ThResponse> RES doRequest(String url, HttpMethod method, Map<String, Object> headers,
			Map<String, Object> queryParams, Map<String, Object> bodyContent, Class<RES> rtClass) throws IOException {
		long startTime = System.currentTimeMillis();
		// 1.创建Request对象，设置一个url地址,设置请求方式。
		HttpUrl httpUrl = this.getHttpUrl(url, queryParams);
		return this.doRequest(startTime, httpUrl, method, headers, bodyContent, rtClass);
	}

	public <RES extends ThResponse> RES doRequest(long startTime, HttpUrl httpUrl, HttpMethod method,
			Map<String, Object> headers, Map<String, Object> bodyContent, Class<RES> rtClass) throws IOException {
		// 2.创建一个call对象,参数就是Request请求对象
		Response response = this.doRequest(startTime, httpUrl, method, headers, bodyContent);
		RES res = null;
		try {
			if (response.isSuccessful()) {
				String body = response.body().string();
				res = this.readValue(body, rtClass);
			} else {
				res = BeanUtils.instantiateClass(rtClass);
				res.setCode(String.valueOf(response.code()));
				res.setMessage(response.message());
			}
		} catch (Exception e) {
			log.error("ThOkHttp3 >> Async Request Error : {}, use time : {}", e.getMessage(),
					System.currentTimeMillis() - startTime);
			res = BeanUtils.instantiateClass(rtClass);
			res.setCode(String.valueOf(response.code()));
			res.setMessage(response.message());
		}
		return res;
	}

	public Response doRequest(long startTime, String url, HttpMethod method, Map<String, Object> headers,
			Map<String, Object> queryParams, Map<String, Object> bodyContent) throws IOException {
		// 1.创建Request对象，设置一个url地址,设置请求方式。
		HttpUrl httpUrl = this.getHttpUrl(url, queryParams);
		return this.doRequest(startTime, httpUrl, method, headers, bodyContent);
	}

	public Response doRequest(long startTime, HttpUrl httpUrl, HttpMethod method, Map<String, Object> headers,
			Map<String, Object> bodyContent) throws IOException {
		// 1、创建Request.Builder对象
		Request.Builder builder = this.createRequestBuilder(httpUrl, method, headers, bodyContent);
		// 2.发送请求
		Response response = null;
		try {
			response = okhttp3Client.newCall(builder.build()).execute();
			if (response.isSuccessful()) {
				if (log.isDebugEnabled()) {
					log.debug("ThOkHttp3 >> Request Success : code : {}, use time : {} ", response.code(),
							System.currentTimeMillis() - startTime);
				}
			} else {
				log.error("ThOkHttp3 >> Request Failure : code : {}, message : {}, use time : {} ", response.code(),
						response.message(), System.currentTimeMillis() - startTime);
			}
		} catch (Exception e) {
			ClientErrorEnum errorEnum = defaultErrorEnum(e);
			// 构造Response对象
			response = new Response.Builder().code(errorEnum.getErrorCode()) // 设置HTTP状态码
					.message(errorEnum.getErrorMessage()) // 设置HTTP状态信息
					.request(builder.build()).protocol(Protocol.HTTP_1_1) // 设置HTTP协议版本
					.header("Content-Type", CommonConstant.APPLICATION_JSON_UTF8_VALUE) // 添加一个响应头
					.body(ResponseBody.create(MediaType.parse(CommonConstant.APPLICATION_JSON_UTF8_VALUE),
							errorEnum.getErrorMessage()))
					.build(); // 设置响应体
			log.error("ThOkHttp3 Request Error : {}, use time : {}", e.getMessage(),
					System.currentTimeMillis() - startTime);
		}
		return response;
	}

	private ClientErrorEnum defaultErrorEnum(Exception e) {
		if (e instanceof SSLHandshakeException) {
			return ClientErrorEnum.SSL_ERROR;
		} else if (e instanceof SSLException) {
			return ClientErrorEnum.SSL_ERROR;
		} else if (e instanceof SocketTimeoutException) {
			return ClientErrorEnum.TIMEOUT_ERROR;
		} else if (e instanceof ConnectException) {
			return ClientErrorEnum.NETWORK_ERROR;
		} else if (e instanceof UnknownHostException) {
			return ClientErrorEnum.NETWORK_ERROR;
		} else if (e instanceof HttpException) {
			return ClientErrorEnum.SERVER_ERROR;
		} else {
			return ClientErrorEnum.UNKNOWN_ERROR;
		}
	}

	public <T extends ThResponse> void doAsyncRequest(String url, HttpMethod method, Consumer<T> success,
			Class<T> rtClass) throws IOException {
		this.doAsyncRequest(url, method, success, null, rtClass);
	}

	public <T extends ThResponse> void doAsyncRequest(String url, HttpMethod method, Consumer<T> success,
			Consumer<IOException> failure, Class<T> rtClass) throws IOException {
		this.doAsyncRequest(url, method, null, success, failure, rtClass);
	}

	public <T extends ThResponse> void doAsyncRequest(String url, HttpMethod method, Map<String, Object> queryParams,
			Consumer<T> success, Consumer<IOException> failure, Class<T> rtClass) throws IOException {
		this.doAsyncRequest(url, method, null, queryParams, success, failure, rtClass);
	}

	public <T extends ThResponse> void doAsyncRequest(String url, HttpMethod method, Map<String, Object> headers,
			Map<String, Object> queryParams, Consumer<T> success, Consumer<IOException> failure, Class<T> rtClass)
			throws IOException {
		this.doAsyncRequest(url, method, headers, queryParams, null, success, failure, rtClass);
	}

	public <T extends ThResponse> void doAsyncRequest(String url, HttpMethod method, Map<String, Object> headers,
			Map<String, Object> queryParams, Map<String, Object> bodyContent, Consumer<T> success,
			Consumer<IOException> failure, Class<T> rtClass) throws IOException {
		long startTime = System.currentTimeMillis();
		// 1.创建Request对象，设置一个url地址,设置请求方式。
		HttpUrl httpUrl = this.getHttpUrl(url, queryParams);
		this.doAsyncRequest(startTime, httpUrl, method, headers, bodyContent, success, failure, rtClass);
	}

	public <T extends ThResponse> void doAsyncRequest(long startTime, HttpUrl httpUrl, HttpMethod method,
			Map<String, Object> headers, Map<String, Object> bodyContent, Consumer<T> success,
			Consumer<IOException> failure, Class<T> rtClass) throws IOException {
		// 2.创建一个call对象,参数就是Request请求对象
		this.doAsyncRequest(startTime, httpUrl, method, headers, bodyContent, (call, response) -> {
			T res;
			try {
				if (response.isSuccessful()) {
					String body = response.body().string();
					res = this.readValue(body, rtClass);
				} else {
					res = BeanUtils.instantiateClass(rtClass);
					res.setCode(String.valueOf(response.code()));
					res.setMessage(response.message());
				}
			} catch (Exception e) {
				log.error("ThOkHttp3 >> Async Request Error : {}, use time : {}", e.getMessage(),
						System.currentTimeMillis() - startTime);
				res = BeanUtils.instantiateClass(rtClass);
				res.setCode(String.valueOf(response.code()));
				res.setMessage(response.message());
			}
			success.accept(res);
			return res;
		}, failure);
	}

	public <T extends ThResponse> void doAsyncRequest(long startTime, String url, HttpMethod method,
			Map<String, Object> headers, Map<String, Object> queryParams, Map<String, Object> bodyContent,
			BiFunction<Call, Response, T> success, Consumer<IOException> failure) throws IOException {
		// 1.创建Request对象，设置一个url地址,设置请求方式。
		HttpUrl httpUrl = this.getHttpUrl(url, queryParams);
		this.doAsyncRequest(startTime, httpUrl, method, headers, bodyContent, success, failure);
	}

	public <T extends ThResponse> void doAsyncRequest(long startTime, HttpUrl httpUrl, HttpMethod method,
			Map<String, Object> headers, Map<String, Object> bodyContent, BiFunction<Call, Response, T> success,
			Consumer<IOException> failure) throws IOException {
		// 1、创建Request.Builder对象
		Request.Builder builder = this.createRequestBuilder(httpUrl, method, headers, bodyContent);
		// 2.创建一个call对象,参数就是Request请求对象
		okhttp3Client.newCall(builder.build()).enqueue(new Callback() {

			@Override
			public void onFailure(Call call, IOException e) {
				log.error("ThOkHttp3 >> Async Request url : {}, Failure : {}, use time : {} ", httpUrl, e.getMessage(),
						System.currentTimeMillis() - startTime);
				if (Objects.nonNull(failure)) {
					failure.accept(e);
				}
			}

			@Override
			public void onResponse(Call call, Response response) {
				if (response.isSuccessful()) {
					if (response.isSuccessful()) {
						if (log.isDebugEnabled()) {
							log.debug("ThOkHttp3 >> Async Request url : {} Success : code : {}, use time : {} ",
									httpUrl, response.code(), System.currentTimeMillis() - startTime);
						}
					}
				} else {
					log.error("ThOkHttp3 >> Async Request url : {} Failure : code : {}, message : {}, use time : {} ",
							httpUrl, response.code(), response.message(), System.currentTimeMillis() - startTime);
				}
				if (Objects.nonNull(success)) {
					success.apply(call, response);
				}
			}

		});
	}

	private HttpUrl getHttpUrl(String httpUrl, Map<String, Object> params) {
		if (log.isDebugEnabled()) {
			log.debug("ThOkHttp3 >> Request Url : {}", httpUrl);
		}
		HttpUrl.Builder urlBuilder = HttpUrl.parse(httpUrl).newBuilder();
		if (CollectionUtils.isEmpty(params)) {
			return urlBuilder.build();
		}
		if (!CollectionUtils.isEmpty(params)) {
			if (log.isDebugEnabled()) {
				log.debug("ThOkHttp3 >> Request Params : {}", params);
			}
			Iterator<Entry<String, Object>> it = params.entrySet().iterator();
			while (it.hasNext()) {
				Entry<String, Object> entry = it.next();
				urlBuilder.addQueryParameter(entry.getKey(),
						Objects.isNull(entry.getValue()) ? "" : entry.getValue().toString());
			}
		}
		return urlBuilder.build();
	}

	private Request.Builder createRequestBuilder(HttpUrl httpUrl, HttpMethod method, Map<String, Object> headers,
			Map<String, Object> bodyContent) throws IOException {
		if (log.isDebugEnabled()) {
			log.debug("ThOkHttp3 >> Request Query Url : {} , Method : {}", httpUrl.query(), method.getName());
		}
		// 1、创建Request.Builder对象
		Request.Builder builder = new Request.Builder().url(httpUrl);
		// 2、添加请求头
		builder = builder.header("Content-Type", CommonConstant.APPLICATION_JSON_UTF8_VALUE);
		if (Objects.nonNull(headers)) {
			if (log.isDebugEnabled()) {
				log.debug("ThOkHttp3 >> Request Headers : {}", headers);
			}
			for (Entry<String, Object> entry : headers.entrySet()) {
				builder.addHeader(entry.getKey(), String.valueOf(entry.getValue()));
			}
		}
		// 3、添加请求体
		if (Objects.nonNull(bodyContent)) {
			String bodyStr = objectMapper.writeValueAsString(bodyContent);
			if (log.isDebugEnabled()) {
				log.debug("ThOkHttp3 >> Request Body : {}", bodyStr);
			}
			builder = method.apply(builder, bodyStr);
		} else {
			builder = method.apply(builder);
		}
		return builder;
	}

	private <T extends ThResponse> T readValue(String json, Class<T> cls) {
		try {
//			return JSONObject.parseObject(json, cls);
			return objectMapper.readValue(json, cls);
		} catch (Exception e) {
			log.error(e.getMessage());
			return BeanUtils.instantiateClass(cls);
		}
	}

}
